using System;
using System.Collections.Generic;
using System.Linq;
using Sustainalytics.ESGRatings.BusinessLogic.Computing;
using Sustainalytics.ESGRatings.BusinessLogic.Computing.Utils;
using Sustainalytics.ESGRatings.DataContract.ESG;
using Sustainalytics.ESGRatings.Computing.Entities;

namespace Sustainalytics.ESGRatings.Computing.Service.Mappers
{
    public class ESGComputeMap
    {
        private const int Top = 5;

        private readonly ICompanyFilter _companyfilter;

        // private readonly IGroupFilter _groupFilter;
        private readonly List<KeyValuePair<int, TopCompaniesInfo>> _groupTopCompanies;
        private readonly ESGComputeResult _result;

        public ESGComputeMap(ESGComputeResult result, ICompanyFilter filter)
        {
            this._result = result;
            this._companyfilter = filter;
            this._groupTopCompanies = new List<KeyValuePair<int, TopCompaniesInfo>>();
        }

        public IEnumerable<GroupRatingDto> GetGroups()
        {
            if (this._result.Ratings == null)
            {
                yield break;
            }

            var groupsCount = this._result.Ratings.ClusterGroupsCount;
            var clustersCount = this._result.Ratings.ClustersCount;
            var ratings = this._result.Ratings;

            for (var i = 0; i < groupsCount; i++)
            {
                var i1 = i;
                var clustersAggregates =
                    Enumerable.Range(0, clustersCount)
                        .Select(ci => new { ci, index = ci * groupsCount + i1 })
                        .Select(
                            @t =>
                            new GroupAggregatesDto
                            {
                                Avg = ratings.Group_Avgs[@t.index],
                                Max = ratings.Group_Maxs[@t.index],
                                Min = ratings.Group_Mins[@t.index]
                            });

                yield return new GroupRatingDto { GroupId = i, ClusterAggregates = clustersAggregates.ToList() };
            }
        }

        public IEnumerable<GroupRatingDto> GetGroupsWithTopCompanies()
        {
            List<GroupRatingDto> result = new List<GroupRatingDto>(0);

            if (this._result.Ratings == null)
            {
                return result;
            }

            var groupsCount = this._result.Ratings.ClusterGroupsCount;
            var clustersCount = this._result.Ratings.ClustersCount;
            var ratings = this._result.Ratings;

            if (this._groupTopCompanies.IsNullOrEmpty())
            {
                var companiesCount = this._result.Ratings.CompaniesCount;
                this.ComputeCompaniesRatings(companiesCount, ratings);
            }

            var topInClusterGroupsDictionary = this._groupTopCompanies.GroupBy(x => x.Key)
                .ToDictionary(g => g.Key, g => g.Select(inner => inner.Value).ToList());

            for (var groupId = 0; groupId < groupsCount; groupId++)
            {
                GroupRatingDto groupRating = new GroupRatingDto
                {
                    GroupId = groupId,
                    ClusterAggregates = new List<GroupAggregatesDto>(),
                    TopCompaniesScoresOnComparativeGroup =
                                                         new List<CompanyScoreDto>()
                };

                foreach (var cluster in Enumerable.Range(0, clustersCount))
                {
                    List<TopCompaniesInfo> group;
                    int index = cluster * groupsCount + groupId;
                    if (topInClusterGroupsDictionary.TryGetValue(groupId, out group))
                    {
                        var cluster1 = cluster;
                        IList<TopCompaniesInfo> groupItem = @group.Where(x => x.ClusterId == cluster1).ToList();
                        var topInfo = groupItem.IsNullOrEmpty()
                                          ? null
                                          : groupItem.Select(
                                              company =>
                                              new CompanyScoreDto()
                                              {
                                                  CompanyId = GetCompanyId(company.CompanyId),
                                                  Rank = company.Rank,
                                                  Score = company.Score,
                                                  Index = company.ClusterId
                                              });
                        if (topInfo != null)
                        {
                            groupRating.TopCompaniesScoresOnComparativeGroup.AddRange(topInfo);
                        }
                    }

                    var groupAggregates = new GroupAggregatesDto
                    {
                        Avg = ratings.Group_Avgs[index],
                        Max = ratings.Group_Maxs[index],
                        Min = ratings.Group_Mins[index]
                    };

                    groupRating.ClusterAggregates.Add(groupAggregates);
                }

                result.Add(groupRating);
            }

            return result;
        }

        public IEnumerable<CompanyIdicatorsDto> GetIndicators()
        {
            if (this._result.Ratings == null)
            {
                yield break;
            }



            var companiesCount = this._result.Ratings.CompaniesCount;
            var indicatorsCount = this._result.Ratings.IndicatorsCount;

            for (var i = 0; i < companiesCount; i++)
            {
                if (!this.IncludeByIndex(i))
                {
                    continue;
                }

                var companyId = this.GetCompanyId(i);

                var companyIndicators = new List<IndicatorScoreStructDto>();

                for (var j = 0; j < indicatorsCount; j++)
                {
                    var indicatorData = this.GetIndicatorScoresStruct(companyId, j);
                    var score = indicatorData.Score;
                    if (Math.Abs(score - (-1)) < 1e-10)
                    {
                        continue;
                    }

                    var index = j * companiesCount + i;

                    if (this._result.MatrixInfo.IsNaN[index])
                    {
                        continue;
                    }

                    var weightedScore = this._result.IsDefaultWeightMatrix ? indicatorData.WeightedScore.GetValueOrDefault() : this._result.Ratings.IndicatorsRatings[index];
                    var weight = (this._result.ComputeOptions & ESGRatingsOptions.IncludeFlags.IncludeWeights) !=0 ? this._result.IsDefaultWeightMatrix ? indicatorData.Weight.GetValueOrDefault() : this._result.Ratings.Weights[index] : 0;

                    companyIndicators.Add(
                        new IndicatorScoreStructDto
                        {
                            Index = j,
                            Score = score,
                            WeightedScore = weightedScore,
                            Weight = weight
                        });
                }

                yield return new CompanyIdicatorsDto { CompanyId = companyId, Indicators = companyIndicators };
            }
        }

        public IEnumerable<CompanyRatingDto> GetRatings()
        {
            if (_result.Ratings == null)
            {
                yield break;
            }

            if ((_result.ComputeOptions & ESGRatingsOptions.IncludeFlags.IncludeRatings) == 0)
            {
                yield break;
            }

            var companiesCount = _result.Ratings.CompaniesCount;
            var ratings = _result.Ratings;

            var companiesRatings = ComputeCompaniesRatings(companiesCount, ratings);

            for (var i = 0; i < companiesCount; i++)
            {
                var companyIndex = ratings.CompanyIndices[i];

                if (IncludeByIndex(companyIndex))
                {
                    yield return companiesRatings[companyIndex];
                }
            }
        }

        public IndicatorsGroupsDto ToIndicatorsGroupsDto()
        {
            var indicatorsGroups = new IndicatorsGroupsDto
            {
                All = new ClusterDto { Id = 0, Name = "All" },
                Themes = new List<ClusterDto>(),
                Clusters = new List<ClusterDto>()
            };

            var index = 1;
            foreach (var themeMap in this._result.Configuration.EsgMap)
            {
                indicatorsGroups.Themes.Add(new ClusterDto { Id = index, Name = themeMap });
                index++;
            }

            foreach (var themeMap in this._result.Configuration.ClustersMap)
            {
                indicatorsGroups.Clusters.Add(new ClusterDto { Id = index, Name = themeMap });
                index++;
            }

            return indicatorsGroups;
        }

        private bool CompanyHasPeersInSelection(int companyId, int ratingIndex)
        {
            return
                !(this._result.Ratings.IsInReferenceUniverse[companyId] == false
                  && this._result.Ratings.Group_Counts[ratingIndex] == 1
                  && this._result.Ratings.Rankings[ratingIndex] == 1);
        }

        private Dictionary<int, CompanyRatingDto> ComputeCompaniesRatings(int companiesCount, RatingsResult ratings)
        {
            var clustersCount = _result.Ratings.ClustersCount;

            var ax = (from ci in Enumerable.Range(0, companiesCount)
                      let companyIndex = ratings.CompanyIndices[ci]
                      select
                          new KeyValuePair<int, CompanyRatingDto>(
                          companyIndex,
                          new CompanyRatingDto { CompanyId = GetCompanyId(companyIndex), CompanyName = GetCompanyName(GetCompanyId(companyIndex)) })).ToDictionary(
                              c => c.Key,
                              c => c.Value);

            for (var j = 0; j < clustersCount; j++)
            {
                for (var i = 0; i < companiesCount; i++)
                {
                    var ratingIndex = j * companiesCount + i;
                    var companyId = ratings.CompanyIndices[ratingIndex];
                    var cr = ax[companyId];
                    var unorderedInfo = ax[i];
                    ComputeCompanyRatings(cr, unorderedInfo, companyId, ratingIndex, j);
                }
            }

            return ax;
        }

        private void ComputeCompanyRatings(
            CompanyRatingDto orderedCompanyRating,
            CompanyRatingDto unorderedCompanyRating,
            int companyId,
            int ratingIndex,
            int clusterIndex)
        {
            orderedCompanyRating.ClustersRatings.Add(this._result.Ratings.Ratings[ratingIndex]);

            if (this._result.Ratings.ComputeAggregatedWeights)
            {
                unorderedCompanyRating.ClustersWeights.Add(this._result.Ratings.AggregatedWeights[ratingIndex]);
            }


            orderedCompanyRating.IsInReferenceUniverse = this._result.Ratings.IsInReferenceUniverse[companyId];

            var groupId = this.GetGroupId(companyId);
            orderedCompanyRating.GroupId = groupId;

            var companyGroupValues = this.CompanyHasPeersInSelection(companyId, ratingIndex)
                                         ? new CompanyGroupValuesDto(
                                               this._result.Ratings.Rankings[ratingIndex],
                                               this._result.Ratings.Percentile[ratingIndex],
                                               this._result.Ratings.Group_Counts[ratingIndex])
                                         : CompanyGroupValuesDto.Default();

            orderedCompanyRating.ClustersAggregates.Add(companyGroupValues);

            if (companyGroupValues.Rank <= Top && this._result.Ratings.IsInReferenceUniverse[companyId])
            {
                this._groupTopCompanies.Add(
                    new KeyValuePair<int, TopCompaniesInfo>(
                        groupId,
                        new TopCompaniesInfo(
                            companyId,
                            companyGroupValues.Rank,
                            clusterIndex,
                            this._result.Ratings.Ratings[ratingIndex])));
            }
        }

        private Guid GetCompanyId(int index)
        {
            return _result.CompaniesMap[index];
        }

        private string GetCompanyName(Guid companyId)
        {
            return _result.CompaniesNames[companyId];
        }

        private int GetGroupId(int companyIndex)
        {
            var heads = this._result.CompaniesHeadsGroups;
            var headsCount = heads.Length;
            for (var i = 0; i < headsCount; i++)
            {
                if (heads[i] > companyIndex)
                {
                    return i;
                }
            }

            return headsCount;
        }

        private float GetIndicatorScore(Guid companyId, int indicatorIndex)
        {
            try
            {
                IndicatorDataStruct[] indicatorsData;
                if (this._result.IndicatorsScores.TryGetValue(companyId, out indicatorsData))
                {
                    var indicator = indicatorsData[indicatorIndex];
                    if (indicator.IsNotNan == 0)
                    {
                        return -1;
                    }

                    return indicator.Score;
                }
            }
            catch (ArgumentOutOfRangeException)
            {
                return -1;
            }

            return -1;
        }

        private IndicatorDataStruct GetIndicatorScoresStruct(Guid companyId, int indicatorIndex)
        {
            try
            {
                IndicatorDataStruct[] indicatorsData;
                if (this._result.IndicatorsScores.TryGetValue(companyId, out indicatorsData))
                {
                    var indicator = indicatorsData[indicatorIndex];
                    if (indicator.IsNotNan == 0)
                    {
                        return IndicatorDataStruct.Default();
                    }

                    return indicator;
                }
            }
            catch (ArgumentOutOfRangeException)
            {
                return IndicatorDataStruct.Default();
            }

            return IndicatorDataStruct.Default();
        }

        private bool IncludeById(Guid companyId)
        {
            return this._companyfilter == null || this._companyfilter.IsMatch(companyId);
        }

        private bool IncludeByIndex(int companyIndex)
        {
            return this.IncludeById(this._result.CompaniesMap[companyIndex]);
        }
    }
}